package code.whmmm.util.spring.aspect.proxy;

import code.whmmm.util.spring.aspect.handler.AspectHandlerChain;
import org.aspectj.lang.ProceedingJoinPoint;
import code.whmmm.util.spring.aspect.annotation.CodeAspect;
import code.whmmm.util.spring.aspect.annotation.CodeAspects;
import code.whmmm.util.spring.aspect.handler.AspectHandler;
import code.whmmm.util.spring.aspect.handler.AspectHandlerResolver;

import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * <pre>{@code
 * // 使用方法
 * AspectProxy.execute(point)
 * }</pre>
 * <br/><br/>
 * 创建时间: 2020/1/10 15:54
 *
 * @author whmmm
 */
public class AspectProxy {
    private static final Map<String, AspectHandlerChain> CACHE_MAP =
            new ConcurrentHashMap<String, AspectHandlerChain>();


    /**
     * 执行环绕连接点 方法
     *
     * @param metaDataAnnotation 注解上含有 {@link CodeAspect}
     *                           或 {@link CodeAspects}注解 的注解
     * @return
     */
    public static Object execute(String cacheName, ProceedingJoinPoint point,
                                 Class<? extends Annotation> metaDataAnnotation)
            throws Throwable {
        AspectHandlerChain chain = CACHE_MAP.get(cacheName);
        if (chain == null) {
            CodeAspects aspectArray = null;
            CodeAspect aspect = metaDataAnnotation.getAnnotation(CodeAspect.class);
            if (aspect == null) {
                // 判断数组形式
                aspectArray = metaDataAnnotation.getAnnotation(CodeAspects.class);
                if (aspectArray == null || aspectArray.value().length == 0) {
                    // 不调用参数。
                    return point.proceed();
                }

                // 不调用参数。
                return point.proceed();
            }

            CodeAspect[] arr = null;
            if (aspect != null) {
                arr = new CodeAspect[]{aspect};
            } else {
                arr = aspectArray.value();
            }

            // 缓存为空。。。
            chain = AspectHandlerResolver.buildChain(arr);

            CACHE_MAP.put(cacheName, chain);
        }

        List<AspectHandler> beforeList = chain.getBeforeList();
        List<AspectHandler> afterList = chain.getAfterList();
        List<AspectHandler> throwList = chain.getThrowList();


        try {

            Object methodResult;


            boolean b = handleBefore(beforeList, point);


            if (!b) {
                // doBefore 返回为 false,不执行 proceed。
                return null;
            }

            methodResult = point.proceed(point.getArgs());

            methodResult = handleAfter(afterList, point, methodResult);

            return methodResult;
        } catch (Throwable e) {
            boolean b = handleThrow(throwList, point, new Exception(e));
            if (b) {
                throw new Exception(e);
            }
            return null;
        }

    }


    /**
     * @param point
     * @param metaDataAnnotation
     * @return
     * @throws Throwable
     * @see #execute(String, ProceedingJoinPoint, Class)
     */
    public static Object execute(ProceedingJoinPoint point,
                                 Class<? extends Annotation> metaDataAnnotation)
            throws Throwable {

        return execute(metaDataAnnotation.getName(), point, metaDataAnnotation);
    }


    private static boolean handleBefore(List<AspectHandler> list, ProceedingJoinPoint point) {
        boolean b;
        // 执行 doBefore 前置通知。
        for (AspectHandler it : list) {
            b = it.doBefore(point);
            if (!b) {
                return false;
            }
        }
        return true;
    }

    private static Object handleAfter(List<AspectHandler> list,
                                      ProceedingJoinPoint point,
                                      Object o) {

        for (AspectHandler a : list) {
            o = a.doAfter(point, o);
        }

        return o;
    }


    private static boolean handleThrow(List<AspectHandler> list,
                                       ProceedingJoinPoint point,
                                       Exception e) {
        boolean b = true;
        for (AspectHandler a : list) {
            b = a.doException(point, e);
        }
        return b;
    }

}
